// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GenericPlatform/GenericPlatformProcess.h"
#include "HAL/Runnable.h"
#include "HAL/RunnableThread.h"
#include "CoreMinimal.h"
#include <Misc/Paths.h>
#include "HAL/PlatformProcess.h"

DECLARE_MULTICAST_DELEGATE_OneParam(FProcResultDelegate, FString)
DECLARE_MULTICAST_DELEGATE(FProcStateDelegate)

/**
 *
 */
class FProcTaskThread final : public FRunnable
{
public:
	explicit FProcTaskThread(const TCHAR* InThreadName, const FString& InProcURL, const FString& InProcParms) : ThreadName(InThreadName), ProcURL(InProcURL), ProcParams(InProcParms) {
		UE_LOG(LogTemp, Log, TEXT("%s %s"), *InProcURL, *InProcParms);
	}

	void Execute()
	{
		WorkThread.Reset(FRunnableThread::Create(this, *ThreadName, 0));
	}

	void Cancel()
	{
		if (ProcHandle.IsValid() && FPlatformProcess::IsApplicationRunning(ProcId))
		{
			FPlatformProcess::TerminateProc(ProcHandle, true);
			ProcHandle.Reset();
			ProcId = 0;
		}
	}

	virtual uint32 Run() override
	{
		if (FPaths::FileExists(ProcURL) && FPlatformProcess::CreatePipe(ReadPipe, WritePipe))
		{

			ProcHandle = FPlatformProcess::CreateProc(*ProcURL, *ProcParams, false, true, true, &ProcId, 0, NULL, WritePipe);
			if (ProcHandle.IsValid() && FPlatformProcess::IsApplicationRunning(ProcId))
				ProcBeginDelegate.Broadcast();

#if 0
			if (ProcHandle.IsValid())
			{
				FPlatformProcess::WaitForProc(ProcHandle);
			}
#endif

			while (ProcHandle.IsValid() && FPlatformProcess::IsApplicationRunning(ProcId))
			{
				FPlatformProcess::Sleep(0.5f);

#if 0
				FString OutputMsg = FPlatformProcess::ReadPipe(ReadPipe);

				TArray<FString> OutResults;
				if (OutputMsg.ParseIntoArray(OutResults, TEXT("\n"), true) > 0)
					ProcResultDelegate.Broadcast(OutResults);
#endif

				// Append this output to the output buffer
				OutputBuffer += FPlatformProcess::ReadPipe(ReadPipe);

				// Output all the complete lines
				int32 LineStartIdx = 0;
				for (int32 Idx = 0; Idx < OutputBuffer.Len(); Idx++)
				{
#if 0
					if (OutputBuffer[Idx] == '\r' || OutputBuffer[Idx] == '\n')
					{
						ProcResultDelegate.Broadcast(OutputBuffer.Mid(LineStartIdx, Idx - LineStartIdx));

						if (OutputBuffer[Idx] == '\r' && Idx + 1 < OutputBuffer.Len() && OutputBuffer[Idx + 1] == '\n')
						{
							Idx++;
						}

						LineStartIdx = Idx + 1;
					}
#endif
					if (OutputBuffer[Idx] == '\n')
					{
						ProcResultDelegate.Broadcast(OutputBuffer.Mid(LineStartIdx, Idx - LineStartIdx));

						if (Idx + 1 < OutputBuffer.Len() && OutputBuffer[Idx + 1] == '\n')
						{
							Idx++;
						}

						LineStartIdx = Idx + 1;
					}
				}

				// Remove all the complete lines from the buffer
				OutputBuffer = OutputBuffer.Mid(LineStartIdx, MAX_int32);
			}

			int32 ReturnCode;
			FPlatformProcess::GetProcReturnCode(ProcHandle, &ReturnCode);
			if (ReturnCode == 0)
				ProcSucceedDelegate.Broadcast();
			else
				ProcFailedDelegate.Broadcast();

		}
		return 0;
	}

	virtual void Stop() override
	{
		if (ProcHandle.IsValid() && FPlatformProcess::IsApplicationRunning(ProcId))
		{
			FPlatformProcess::TerminateProc(ProcHandle, true);
			ProcHandle.Reset();
			ProcId = 0;
		}
	}

	virtual void Exit() override
	{
		if (ProcHandle.IsValid())
		{
			FPlatformProcess::CloseProc(ProcHandle);
			FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
		}
	}

public:

	FProcResultDelegate ProcResultDelegate;
	FProcStateDelegate ProcBeginDelegate;
	FProcStateDelegate ProcSucceedDelegate;
	FProcStateDelegate ProcFailedDelegate;



private:


	FString ThreadName;
	TUniquePtr<FRunnableThread> WorkThread;

	FString ProcURL;
	FString ProcParams;
	void* ReadPipe;
	void* WritePipe;
	uint32 ProcId;
	FProcHandle ProcHandle;
	FString OutputBuffer;
};
